#include "../lib/webfrontend.hpp"

#include <stdio.h>
#include <string.h>

#include <unistd.h>

#include <fstream>
#include <string>
#include <cerrno>
#include <sstream>

#include <boost/filesystem.hpp>

#include <coreconfigdefs.hpp>
#include <coreutil.hpp>

#define USE_WEBSOCKET

#include "../lib/mongoose.h"
#include "../lib/control.hpp"
#include "../lib/untar.h"
#include "../lib/defines.hpp"

static std::stringstream logstream;
static std::string htmlTemplate;

std::string get_file_contents(const char *filename)
{
    std::ifstream in(filename, std::ios::in | std::ios::binary);
    if (in)
    {
        std::string contents;
        in.seekg(0, std::ios::end);
        contents.resize(in.tellg());
        in.seekg(0, std::ios::beg);
        in.read(&contents[0], contents.size());
        in.close();
        return(contents);
    }
    throw(errno);
}

void replaceAll(std::string& str, const std::string& from, const std::string& to) {
    if(from.empty())
        return;
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}

void addCommon(std::string &file)
{
    char hostname[30];
    gethostname(hostname, 30);
    replaceAll(file,INSTANCE_TAG,hostname);
    replaceAll(file,VERSION_TAG, pland::CORE_VERSION);
}

// This function will be called by mongoose on every new request.
static int begin_request_handler(struct mg_connection *conn)
{
    try
    {
        const struct mg_request_info *request_info = mg_get_request_info(conn);
        std::string file = htmlTemplate;
        addCommon(file);
        if (!strcmp(request_info->uri, "/upload"))
        {
            std::string refresh = REFRESH_PAGE;
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      refresh.length());
            mg_printf(conn, "%s", refresh.c_str());
            mg_upload(conn, "/tmp");
            return 1;
        }
        if (!strcmp(request_info->uri, "/"))
        {
            replaceAll(file, JS_TAG, " <script src=\"/smoothie.js\"></script><script src=\"/sigma.js\"></script><script src=\"/sigma.forceatlas2.js\"></script>");
            replaceAll(file, BODY_TAG,OVERVIEW_PAGE);
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      file.length());
            mg_printf(conn, "%s", file.c_str());
            return 1;
        }
        if (!strcmp(request_info->uri, "/run"))
        {

            Controler* con = Controler::Instance();
            if(con->getState()== Controler::FILES)
            {
                std::string form = std::string("<h3>Step 1: Upload files</h3><br>\n") + UPLOAD_FORM;
                replaceAll(file, BODY_TAG,form);
            }
            if(con->getState()== Controler::LISTEN)
            {
                std::string form = std::string("<h3>Step 2: Start the Transport</h3><br>\n") + LISTEN_FORM +"<br>"+ RESET_FORM;
                form += "<br> <p>Transport should be started across all hosts.</p>";
                replaceAll(file, BODY_TAG,form);
            }
            if(con->getState()== Controler::STOP || con->getState() == Controler::PAUSE)
            {
                std::string form = std::string("<h3>Step 3: Start this chain</h3><br>\n") + START_FORM +"<br>"+ RESET_FORM;
                form += "<br> <p>Might be a good idea to check the logs.</p>";
                replaceAll(file, BODY_TAG,form);
            }
            if(con->getState()== Controler::RUN)
            {
                std::string form = std::string("<h3>Step 4: Chain is Running</h3><br>\n") + STOP_FORM +"<br>"+ RESET_FORM;
                form += "<br> <p>Might be a good idea to check the logs.</p>";
                replaceAll(file, BODY_TAG,form);
            }
            if(con->getState()== Controler::ERROR)
            {
                replaceAll(file, BODY_TAG, ERROR_DIV);
            }
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      file.length());
            mg_printf(conn, "%s", file.c_str());
            return 1;
        }
        if(!strcmp(request_info->uri, "/log"))
        {
            std::string log = logstream.str();
            replaceAll(log,"\n","<br>");
            log = "<h3>System Log:</h3><br>\n<div id =\"code\">" + log + "</div><br><br><hr><br>";
            log+= RELOAD_BUTTON;
            replaceAll(file, BODY_TAG, log);
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      file.length());

            mg_printf(conn, "%s", file.c_str());
            return 1;
        }
        if(!strcmp(request_info->uri, "/reset"))
        {
            Controler* con = Controler::Instance();
            std::string refresh = REFRESH_PAGE;
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      refresh.length());
            mg_printf(conn, "%s", refresh.c_str());
            con->resetState();
            return 1;
        }
        if(!strcmp(request_info->uri, "/listen"))
        {
            Controler* con = Controler::Instance();
            std::string refresh = REFRESH_PAGE;
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      refresh.length());
            mg_printf(conn, "%s", refresh.c_str());
            con->startListner();
            return 1;
        }
        if(!strcmp(request_info->uri, "/start"))
        {
            Controler* con = Controler::Instance();
            std::string refresh = REFRESH_PAGE;
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      refresh.length());
            mg_printf(conn, "%s", refresh.c_str());
            con->start();
            return 1;
        }
        if(!strcmp(request_info->uri, "/stop"))
        {
            Controler* con = Controler::Instance();
            std::string refresh = REFRESH_PAGE;
            mg_printf(conn,
                      "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n",
                      refresh.length());
            mg_printf(conn, "%s", refresh.c_str());
            con->pause();
            return 1;
        }
        if(!strcmp(request_info->uri, "/latency"))
        {
            Controler* con = Controler::Instance();
            std::string latency = pland::utill_to_string(con->getAvgLatency());
            mg_printf(conn,
                 "HTTP/1.1 200 OK\r\n"
                 "Cache: no-cache\r\n"
                 "Content-Type: text/plain\r\n"
                 "Content-Length: %d\r\n"
                 "\r\n",
                 latency.length());
             mg_write(conn, latency.c_str(), latency.length());
             return 1;
        }
        if(!strcmp(request_info->uri, "/throughput"))
        {
            Controler* con = Controler::Instance();
            std::string throughput = pland::utill_to_string(con->getAvgThroughput());
            mg_printf(conn,
                 "HTTP/1.1 200 OK\r\n"
                 "Cache: no-cache\r\n"
                 "Content-Type: text/plain\r\n"
                 "Content-Length: %d\r\n"
                 "\r\n",
                 throughput.length());
             mg_write(conn, throughput.c_str(), throughput.length());
             return 1;
        }
        if(!strcmp(request_info->uri, "/chain"))
        {
            std::string chainFile;
            if(boost::filesystem::exists(std::string(UNTAR_PATH) + CHAIN_FILE))
            {
                chainFile = get_file_contents((std::string(UNTAR_PATH) + CHAIN_FILE).c_str());
            }
            mg_printf(conn,
                 "HTTP/1.1 200 OK\r\n"
                 "Cache: no-cache\r\n"
                 "Content-Type: text/xml\r\n"
                 "Content-Length: %d\r\n"
                 "\r\n",
                 chainFile.length());
            mg_write(conn, chainFile.c_str(), chainFile.length());
            return 1;
        }
    }
    catch(pland::Exeption &e)
    {
        pland::utill_log(std::string(__FILE__) + " Its Over " + e.get_error());
        pland::utill_flush_log();
        std::ofstream outFile;
        outFile.open("log.txt");
        outFile << logstream.str();
    }
    return 0;
}

static void upload_handler(struct mg_connection *conn, const char *filePath)
{
    std::string path = UNTAR_PATH;
    std::string ipcPath;
    if(boost::filesystem::exists(UNTAR_PATH))
    {
        if(boost::filesystem::is_empty(UNTAR_PATH))
            boost::filesystem::remove(UNTAR_PATH);
        else
            boost::filesystem::remove_all(UNTAR_PATH);
    }
    boost::filesystem::create_directory(boost::filesystem::path(UNTAR_PATH));
    FILE* f = fopen(filePath, "r");
    if(!untar(f, UNTAR_PATH))
    {
        fclose(f);
        pland::utill_log(std::string(__FILE__) + " Could not extract file " + filePath);
        ::unlink(filePath);
        return;
    }
    fclose(f);
    ::unlink(filePath);
    pland::utill_log(std::string(__FILE__) + " Extracted " + filePath);
    if(!boost::filesystem::exists(path + CHAIN_FILE))
    {
        pland::utill_log(std::string(__FILE__) + " No chain file in the archive " + filePath);
        return;
    }
    if(!boost::filesystem::exists(path + ROUTE_FILE))
    {
        pland::utill_log(std::string(__FILE__) + " No route file in the archive " + filePath);
        return;
    }
    if(!boost::filesystem::exists(path + ROUTE_FILE))
    {
        pland::utill_log(std::string(__FILE__) + " No route file in the archive " + filePath);
        return;
    }
    ipcPath = path + PIPC_FILE;
    if(!boost::filesystem::exists(ipcPath))
    {
        pland::utill_log(std::string(__FILE__) + " No ipc file provided, not setting up ipc");
        ipcPath = "";
    }
    Controler* con = Controler::Instance();
    con->loadFiles(path+CHAIN_FILE, path+ROUTE_FILE, ipcPath);
}


void start_webserver()
{
    struct mg_context *ctx;
    struct mg_callbacks callbacks;

    pland::utill_set_log(&logstream);

    htmlTemplate = get_file_contents("resource/template.html");

    const char *options[] = {"listening_ports", "8080","document_root","./resource","num_threads","1", NULL};

    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.begin_request = begin_request_handler;
    callbacks.upload = upload_handler;

    // Start the web server.
    ctx = mg_start(&callbacks, NULL, options);
    pland::utill_log(std::string(__FILE__) + " Starting Web Interface");

    getchar();
    mg_stop(ctx);
}
